package view.content;

import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.image.BufferedImage;
import java.io.File;
import java.util.LinkedList;
import java.util.Queue;
import java.util.logging.Level;

import model.Preference;
import model.Settings;
import model.contents.Content;
import platform.Platform;
import util.EmailSending;
import util.ExceptionTracker;
import util.LoggingUtils;

import com.xuggle.xuggler.Global;
import com.xuggle.xuggler.ICodec;
import com.xuggle.xuggler.IContainer;
import com.xuggle.xuggler.IPacket;
import com.xuggle.xuggler.IPixelFormat;
import com.xuggle.xuggler.IStream;
import com.xuggle.xuggler.IStreamCoder;
import com.xuggle.xuggler.IVideoPicture;
import com.xuggle.xuggler.IVideoResampler;
import com.xuggle.xuggler.Utils;

public class XuggleVideoBox extends ContentBox {

	public static final String XUGGLER_LIBRARY_LIB = "/usr/local/xuggler/lib";
	public static final String XUGGLER_LIBRARY_BIN = "/usr/local/xuggler/bin";

	private Queue<IVideoPicture> frames;
	// static {
	// // TODO check the directories, if they don't exist, send error message
	// System.out.println("set xuggler path for platform " +
	// Platform.getPlatform());
	// if(Platform.getPlatform()==Platform.WINDOWS) {
	//
	// } else {
	// // add the default library
	// System.setProperty("java.library.path",
	// XUGGLER_LIBRARY_BIN+":"+System.getProperty("java.library.path"));
	// System.setProperty("java.library.path",
	// XUGGLER_LIBRARY_LIB+":"+System.getProperty("java.library.path"));
	// System.setProperty("LD_LIBRARY_PATH",
	// XUGGLER_LIBRARY_LIB+":"+System.getProperty("LD_LIBRARY_PATH"));
	// System.out.println(System.getProperty("java.library.path"));
	// }
	//
	// }

	private BufferedImage videoFrame;
	public int maxBufferSize;
	public int minBufferSize;

	public XuggleVideoBox(Content c, int width, int height) {
		super(c, width, height);
		this.init();
	}

	private void init() {
		this.setSize(new Dimension(this.getWidth(), this.getHeight()));
		this.setLayout(new BorderLayout());
		this.setIgnoreRepaint(true);
		this.setDoubleBuffered(true);
		this.frames = new LinkedList<IVideoPicture>();
		this.maxBufferSize = 100;
		this.minBufferSize = 50;
	}

	@Override
	public void paintComponent(Graphics g) {

		if (this.videoFrame != null) {
			g.drawImage(this.videoFrame, 0, 0, null);
		} else {
			// no frame to display
		}
	}

	@Override
	public void play() {
		if (Platform.getPlatform() == Platform.LINUX || Platform.getPlatform() == Platform.MAC) {
			File libPath = new File(XUGGLER_LIBRARY_BIN);
			if (!libPath.exists() || !libPath.isDirectory()) {
				// library is not installed;
				System.err.println("The xuggler library is not installed.");
				return;
			}
		}

		try
		{
			// Let's make sure that we can actually convert video pixel formats.
			if (!IVideoResampler.isSupported(IVideoResampler.Feature.FEATURE_COLORSPACECONVERSION)) {
				System.err.println("you must install the GPL version" + " of Xuggler (with IVideoResampler support) for "
						+ "this to work");
				LoggingUtils.log(Level.SEVERE, "you must install the GPL version" + " of Xuggler (with IVideoResampler support) for "
						+ "this to work");
			}
			FramePlayingRunnable framePlayer = new FramePlayingRunnable(this.frames);
			Thread videoPlayingThread = new Thread(framePlayer);
			videoPlayingThread.start();
			// Create a Xuggler container object
			IContainer container = IContainer.make();

			// Open up the container
			String filename = this.getContent().getLocalPath();

			if (container.open(filename, IContainer.Type.READ, null) < 0) {
				String msg = "Xuggler could not open file: " + filename;
				System.err.println(msg);
				return;
			}

			// query how many streams the call to open found
			int numStreams = container.getNumStreams();

			// and iterate through the streams to find the first video stream
			int videoStreamId = -1;
			IStreamCoder videoCoder = null;
			for (int i = 0; i < numStreams; i++) {
				// Find the stream object
				IStream stream = container.getStream(i);
				// Get the pre-configured decoder that can decode this stream;
				IStreamCoder coder = stream.getStreamCoder();
				coder.setNumPicturesInGroupOfPictures(1);
				coder.getFrameRate().setNumerator(10);

				System.out.println(coder.getFrameRate() + " " + coder.getNumPicturesInGroupOfPictures());

				if (coder.getCodecType() == ICodec.Type.CODEC_TYPE_VIDEO) {
					videoStreamId = i;
					videoCoder = coder;
					break;
				}
			}

			if (videoStreamId == -1) {
				System.err.println("could not find video stream in container: " + filename);
				return;
			}

			/*
			 * Now we have found the video stream in this file. Let's open up our
			 * decoder so it can do work.
			 */
			if (videoCoder.open() < 0) {
				System.err.println("could not open video decoder for container: " + filename);
				return;
			}

			IVideoResampler resampler = null;
			if (videoCoder.getPixelType() != IPixelFormat.Type.BGR24) {
				// if this stream is not in BGR24, we're going to need to
				// convert it. The VideoResampler does that for us.
				resampler = IVideoResampler.make(this.width, this.height, IPixelFormat.Type.BGR24, videoCoder.getWidth(),
						videoCoder.getHeight(), videoCoder.getPixelType());
				if (resampler == null) {
					System.err.println("could not create color space " + "resampler for: " + filename);
					return;
				}
			}

			/*
			 * Now, we start walking through the container looking at each packet.
			 */
			IPacket packet = IPacket.make();
			long firstTimestampInStream = Global.NO_PTS;
			long systemClockStartTime = 0;
			while (container.readNextPacket(packet) >= 0) {

				/*
				 * Now we have a packet, let's see if it belongs to our video stream
				 */
				if (packet.getStreamIndex() == videoStreamId) {
					/*
					 * We allocate a new picture to get the data out of Xuggler
					 */
					IVideoPicture picture = IVideoPicture.make(videoCoder.getPixelType(), this.getWidth(),
							videoCoder.getHeight());

					int offset = 0;
					while (offset < packet.getSize()) {
						/*
						 * Now, we decode the video, checking for any errors.
						 */
						int bytesDecoded = videoCoder.decodeVideo(picture, packet, offset);
						if (bytesDecoded < 0) {
							System.err.println("got error decoding video in: " + filename);
							return;
						}
						offset += bytesDecoded;

						/*
						 * Some decoders will consume data in a packet, but will not
						 * be able to construct a full video picture yet. Therefore
						 * you should always check if you got a complete picture
						 * from the decoder
						 */
						if (picture.isComplete()) {
							IVideoPicture newPic = picture;
							/*
							 * If the resampler is not null, that means we didn't
							 * get the video in BGR24 format and need to convert it
							 * into BGR24 format.
							 */
							if (resampler != null) {
								// we must resample
								newPic = IVideoPicture.make(resampler.getOutputPixelFormat(), this.width, this.height);
								if (resampler.resample(newPic, picture) < 0) {
									System.err.println("could not resample video from: " + filename);
									return;
								}

							}
							if (newPic.getPixelType() != IPixelFormat.Type.BGR24) {
								System.err.println("could not decode video" + " as BGR 24 bit data in: " + filename);
								return;
							}

							/**
							 * We could just display the images as quickly as we
							 * decode them, but it turns out we can decode a lot
							 * faster than you think.
							 * 
							 * So instead, the following code does a poor-man's
							 * version of trying to match up the frame-rate
							 * requested for each IVideoPicture with the system
							 * clock time on your computer.
							 * 
							 * Remember that all Xuggler IAudioSamples and
							 * IVideoPicture objects always give timestamps in
							 * Microseconds, relative to the first decoded item. If
							 * instead you used the packet timestamps, they can be
							 * in different units depending on your IContainer, and
							 * IStream and things can get hairy quickly.
							 */
							synchronized (this.frames) {
								if (this.frames.size() < this.maxBufferSize) {
									this.frames.add(newPic);
								} else {
									try {

										this.frames.wait();

									} catch (InterruptedException e) {
										// TODO Auto-generated catch block
										e.printStackTrace();
									}
								}
							}
							// if (firstTimestampInStream == Global.NO_PTS) {
							// // This is our first time through
							// firstTimestampInStream = picture.getTimeStamp();
							// // get the starting clock time so we can hold up
							// // frames
							// // until the right time.
							// systemClockStartTime = System.currentTimeMillis();
							// } else {
							// long systemClockCurrentTime =
							// System.currentTimeMillis();
							// long millisecondsClockTimeSinceStartofVideo =
							// systemClockCurrentTime - systemClockStartTime;
							// // compute how long for this frame since the first
							// // frame in the
							// // stream.
							// // remember that IVideoPicture and IAudioSamples
							// // timestamps are
							// // always in MICROSECONDS,
							// // so we divide by 1000 to get milliseconds.
							// long millisecondsStreamTimeSinceStartOfVideo =
							// (picture.getTimeStamp() - firstTimestampInStream) /
							// 1000;
							// final long millisecondsTolerance = 50; // and we
							// // give
							// // ourselfs
							// // 50 ms of
							// // tolerance
							// final long millisecondsToSleep =
							// (millisecondsStreamTimeSinceStartOfVideo -
							// (millisecondsClockTimeSinceStartofVideo +
							// millisecondsTolerance));
							// if (millisecondsToSleep > 0) {
							// try {
							// Thread.sleep(millisecondsToSleep);
							// } catch (InterruptedException e) {
							// // we might get this when the user closes
							// // the dialog box, so
							// // just return from the method.
							// return;
							// }
							// }
							// }
							//
							// // And finally, convert the BGR24 to an Java buffered
							// // image
							// BufferedImage javaImage =
							// Utils.videoPictureToImage(newPic);
							// this.videoFrame = javaImage;
							//
							// // and display it on the Java Swing window
							// this.repaint();
						}
					}
				} else {
					/*
					 * This packet isn't part of our video stream, so we just
					 * silently drop it.
					 */
					do {
					} while (false);
				}

			}
			/*
			 * Technically since we're exiting anyway, these will be cleaned up by
			 * the garbage collector... but because we're nice people and want to be
			 * invited places for Christmas, we're going to show how to clean up.
			 */
			framePlayer.setAllFramesReceived(true);
			try {
				videoPlayingThread.join();
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
				LoggingUtils.log(Level.SEVERE, "XuggleVideoBox () "+e.getMessage(),e);
			}
			if (videoCoder != null) {
				videoCoder.close();
				videoCoder = null;
			}
			if (container != null) {
				container.close();
				container = null;
			}

		}
		catch(Exception e)
		{
			LoggingUtils.log(Level.SEVERE,"Video BOX Name: = "+content.getLocalPath()+"\n",e);
			
			if(!ExceptionTracker.isExceptionNotifiedByKey("XugglerException"))
			{
				ExceptionTracker.setExceptionInTracker("XugglerException",e.getMessage());
				
				final String exceptionmessage = EmailSending.FormatException(e);
				Thread emailingThread = new Thread(new Runnable() {
					@Override
					public void run() {
	
						String EmailBody = EmailSending.ProcessEmailBody(content.getLocalPath()+" Video has formating problem or xuggler library problem", 
								"xuggler error:Notification, Video not playing", exceptionmessage);
	
						util.EmailSending.getInstance().send(Settings.TO_USER, "xuggler error:Notification" +
								"BoxID="+Preference.getInstance().getBoxID(), EmailBody);
					}
				});
				emailingThread.setName("Emailing");
				emailingThread.start();
			}
		}
		catch(Error er)
		{
			LoggingUtils.log(Level.SEVERE,"Video BOX Name: = "+content.getLocalPath()+"\n",er);
			
			if(!ExceptionTracker.isExceptionNotifiedByKey("XugglerError"))
			{
				ExceptionTracker.setExceptionInTracker("XugglerError",er.getMessage());
				
				final String exceptionmessage = EmailSending.FormatException(er);
				Thread emailingThread = new Thread(new Runnable() {
					@Override
					public void run() {
	
						String EmailBody = EmailSending.ProcessEmailBody(content.getLocalPath()+" no library path ", 
								"xuggler error:Notification, Video not playing", exceptionmessage);
	
						util.EmailSending.getInstance().send(Settings.TO_USER, "xuggler error:Notification" +
								"BoxID="+Preference.getInstance().getBoxID(), EmailBody);
					}
				});
				emailingThread.setName("Emailing");
				emailingThread.start();
			}
		}
	}

	private class FramePlayingRunnable implements Runnable {

		private Queue<IVideoPicture> frames;
		private boolean allFramesReceived;

		public FramePlayingRunnable(Queue<IVideoPicture> frames) {
			this.frames = frames;
		}

		@Override
		public void run() {
			long firstTimestampInStream = Global.NO_PTS;
			long systemClockStartTime = 0;
			long lastframeTimeStamp = Global.NO_PTS;
			boolean wait = true;
			IVideoPicture picture;
			while (!allFramesReceived) {
				synchronized (this.frames) {
					while (!this.frames.isEmpty()) {
						picture = this.frames.poll();
						if (this.frames.size() < XuggleVideoBox.this.minBufferSize) {
							this.frames.notifyAll();
						}
						// System.out.println("number of frames buffered: " + this.frames.size());

						if (firstTimestampInStream == Global.NO_PTS) {
							// This is our first time through
							firstTimestampInStream = picture.getTimeStamp();
							// get the starting clock time so we can hold up
							// frames
							// until the right time.
							systemClockStartTime = System.currentTimeMillis();
						} else {
							long systemClockCurrentTime = System.currentTimeMillis();
							long millisecondsClockTimeSinceStartofVideo = systemClockCurrentTime - systemClockStartTime;
							// compute how long for this frame since the first
							// frame in the
							// stream.
							// remember that IVideoPicture and IAudioSamples
							// timestamps are
							// always in MICROSECONDS,
							// so we divide by 1000 to get milliseconds.
							long millisecondsStreamTimeSinceStartOfVideo = (picture.getTimeStamp() - firstTimestampInStream) / 1000;
							final long millisecondsTolerance = 50; // and we
							// give
							// ourselfs
							// 50 ms of
							// tolerance
							final long millisecondsToSleep = (millisecondsStreamTimeSinceStartOfVideo - (millisecondsClockTimeSinceStartofVideo + millisecondsTolerance));
							if (millisecondsToSleep > 0) {
								try {
									Thread.sleep(millisecondsToSleep);
								} catch (InterruptedException e) {
									// we might get this when the user closes
									// the dialog box, so
									// just return from the method.
									LoggingUtils.log(Level.SEVERE, "PlatformCommandExecutor.restartPlayer()",e);
									return;
								}
							}
						}
						BufferedImage javaImage = Utils.videoPictureToImage(picture);
						XuggleVideoBox.this.videoFrame = javaImage;
						XuggleVideoBox.this.repaint();
						lastframeTimeStamp = picture.getTimeStamp();

					}
				}
			}
		}

		public boolean isAllFramesReceived() {
			return allFramesReceived;
		}

		public void setAllFramesReceived(boolean allFramesReceived) {
			this.allFramesReceived = allFramesReceived;
		}
	}
}
